package com.waterwolf.wolfbot;

import java.io.File;
import java.util.HashMap;
import java.util.Iterator;

import org.jdom.Element;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;

import com.rshacking.rhf.files.XMLContainer;
import com.rshacking.rhf.updater.hooks.ClassHook;
import com.rshacking.rhf.updater.hooks.FieldHook;
import com.rshacking.rhf.updater.hooks.HooksList;

public class HookLoader {

    HooksList hookList = new HooksList("com.waterwolf.wolfbot.internal");

    public HookLoader() {

        final File hooksXml = new File("archive/hooks/hooks_latest.xml");
        importXML(new XMLContainer(hooksXml));
    }

    /**
     * Gets an existing classhook.
     * 
     * @param name the new name the class was given
     * @return null if the hook doesn't exist
     */
    public ClassHook get(final String name) {
        final Iterator<ClassHook> it = hookList.iterator();
        while (it.hasNext()) {
            final ClassHook ch = it.next();
            if (ch.getName().equals(name))
                return ch;
        }
        return null;
    }

    /**
     * Gets an existing classhook.
     * 
     * @param name the new name the class was given
     * @return null if the hook doesn't exist
     */
    public ClassHook getObfuscated(final String name) {
        final Iterator<ClassHook> it = hookList.iterator();
        while (it.hasNext()) {
            final ClassHook ch = it.next();
            if (ch.getOrigName().equals(name))
                return ch;
        }
        return null;
    }

    public ClassHook add(final String name, final String origName) {
        final ClassHook ch = get(name);
        if ((ch != null))
            return ch;
        String parsed = origName;
        if (parsed.endsWith(";")) {
            parsed = parsed.replace("[", "").substring(1);
            parsed = parsed.substring(0, parsed.length() - 1);
        }
        return hookList.add(name, parsed);
    }

    /**
     * Imports hooks from an XML file.
     * 
     * @param xml the xml container which contains the xml with the hooks
     */
    public void importXML(final XMLContainer xml) {
        hookList.setOverride(true); // TODO
        final Iterator<?> ci = xml.getDocument().getRootElement().getChildren(
                "class").iterator();
        while (ci.hasNext()) {
            Object o = ci.next();
            if (o instanceof Element) {
                final Element ce = (Element) o;
                final Element cne = ce.getChild("name");
                final ClassHook ch = add(cne.getAttributeValue("new"),
                        cne.getText());
                final Iterator<?> fi = ce.getChildren("field").iterator();
                while (fi.hasNext()) {
                    o = fi.next();
                    if (o instanceof Element) {
                        final Element fe = (Element) o;
                        final Element fne = fe.getChild("name");
                        //System.out.println(fe + " " + fne);
                        final boolean _static = Boolean.parseBoolean(fe.getChildText("static"));
                        final FieldInsnNode fn = new FieldInsnNode(_static ? Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC : Opcodes.ACC_PUBLIC, fe.getChildText("parent"), fne.getText(), fe.getChildText("type"));
                        fn.setOpcode(_static ? Opcodes.GETSTATIC : Opcodes.GETFIELD);
                        ch.add(fne.getAttributeValue("new"), fn);
                        /*
                        ch.add(
                                fne.getAttributeValue("new"),
                                fne.getText(),
                                fe.getChildText("type"),
                                Boolean.parseBoolean(fe.getChildText("static")));*/
                    }
                }
                
                final Iterator<?> mi = ce.getChildren("method").iterator();
                while (mi.hasNext()) {
                    o = mi.next();
                    if (o instanceof Element) {
                        final Element me = (Element) o;
                        final Element mne = me.getChild("name");
                        ch.addMethod(mne
                                .getAttributeValue("new"), mne.getText(), me
                                .getChildText("type"), Boolean.parseBoolean(me
                                        .getChildText("static")));
                    }
                }
            }
        }
    }

    /**
     * Converts a signature to a new one taking in account all hooked classes.
     * If a the type is of a hooked class it will return the new class type.
     * 
     * @param type the original type
     * @return the converted type
     */
    public String typeOrigToNew(final String type) {
        return hookList.typeOrigToNew(type);
    }

    public HashMap<String, Class<?>> classes = new HashMap<String, Class<?>>();

    public byte[] hack(final byte[] bytes) {
        final ClassReader cr = new ClassReader(bytes);
        final ClassNode cn = new ClassNode();
        cr.accept(cn, ClassReader.SKIP_DEBUG
                | ClassReader.SKIP_FRAMES);
        
        final ClassHook ich = getObfuscated(cn.name);
        if (ich != null) {

            String tmp_Package = BasicLoader._package.replace('.', '/');
            if (!(tmp_Package.trim().equals("") || tmp_Package.endsWith("/"))) {
                tmp_Package += "/";
            }
            
            if (ich.getName().equals("KeyboardInput")) {
                /*
                cn.superName = "com/waterwolf/wolfbot/internal/input/KeyboardInput";
                System.out.println("kbinput hacked");
                for (final MethodNode mn : cn.methods) {
                    if (mn.name.equals("<init>")) {
                        final Iterator<AbstractInsnNode> insnNodes = mn.instructions.iterator();
                        while (insnNodes.hasNext()) {
                            final AbstractInsnNode ain = insnNodes.next();
                            if (ain instanceof MethodInsnNode) {
                                final MethodInsnNode node = (MethodInsnNode) ain;
                                if (node.getOpcode() == Opcodes.INVOKESPECIAL) {
                                    if (node.owner.equals("java/lang/Object")) {
                                        node.owner = "com/waterwolf/wolfbot/internal/input/KeyboardInput";
                                        System.out.println("kbinvoke hacked");
                                    }
                                    //System.out.println(node.name + " " + node.owner + " " + node.desc);
                                }
                             }
                        }
                    }
                }*/
            }
            else if (ich.getName().equals("Keyboard")) {
                /*
                final Iterator<MethodNode> mni = cn.methods.iterator();
                while (mni.hasNext()) {
                    final MethodNode mn = mni.next();
                    if (mn.name.startsWith("key")) {
                        mn.name = "_" + mn.name;
                    }
                }
                cn.interfaces.clear();
                System.out.println("Keyboard hacked");
                */
            }
            else if (ich.getName().equals("MouseInput")) {
                cn.superName = "com/waterwolf/wolfbot/internal/input/MouseInput";
                System.out.println("im hacked");
                for (final MethodNode mn : cn.methods) {
                    if (mn.name.equals("<init>")) {
                        final Iterator<AbstractInsnNode> insnNodes = mn.instructions.iterator();
                        while (insnNodes.hasNext()) {
                            final AbstractInsnNode ain = insnNodes.next();
                            if (ain instanceof MethodInsnNode) {
                                final MethodInsnNode node = (MethodInsnNode) ain;
                                if (node.getOpcode() == Opcodes.INVOKESPECIAL) {
                                    if (node.owner.equals("java/lang/Object")) {
                                        node.owner = "com/waterwolf/wolfbot/internal/input/MouseInput";
                                        System.out.println("mouseinvoke hacked");
                                    }
                                    //System.out.println(node.name + " " + node.owner + " " + node.desc);
                                }
                             }
                        }
                    }
                }
            }
            else if (ich.getName().equals("Mouse")) {
                
                final Iterator<MethodNode> mni = cn.methods.iterator();
                while (mni.hasNext()) {
                    final MethodNode mn = mni.next();
                    if (mn.name.startsWith("mouse")) {
                        mn.name = "_" + mn.name;
                    }
                }
                cn.interfaces.clear();
                System.out.println("Mouse hacked");
            }
            else if (ich.getName().equals("Canvas")) {
                cn.superName = "com/waterwolf/wolfbot/internal/input/CanvasWrapper";
                
                for (final MethodNode mn : cn.methods) {
                    if (mn.name.equals("<init>")) {
                        final Iterator<AbstractInsnNode> insnNodes = mn.instructions.iterator();
                        while (insnNodes.hasNext()) {
                            final AbstractInsnNode ain = insnNodes.next();
                            if (ain instanceof MethodInsnNode) {
                                final MethodInsnNode node = (MethodInsnNode) ain;
                                if (node.getOpcode() == Opcodes.INVOKESPECIAL) {
                                    if (node.owner.equals("java/awt/Canvas")) {
                                        node.owner = "com/waterwolf/wolfbot/internal/input/CanvasWrapper";
                                        System.out.println("canvasinvoke hacked");
                                    }
                                    //System.out.println(node.name + " " + node.owner + " " + node.desc);
                                }
                             }
                        }
                    }
                }
                
                System.out.println("Canvas hacked");
            }
            else {
                if (!cn.interfaces.contains(tmp_Package + ich.getName())) {
                    cn.interfaces.add(tmp_Package + ich.getName());
                }
            }
            

            final Iterator<FieldHook> ft = ich.iterator();
            while (ft.hasNext()) {
                final FieldHook ifh = ft.next();

                if (!containsMethod(cn, ifh.getGetterName(), "()"
                        + typeOrigToNew(ifh.getOrigType()), ifh.isStatic())) {
                    
                    new com.rshacking.rhf.updater.hooks.HookGetAdapter(cn, ifh).visitEnd();
                }

            }
            //System.out.println(cn.name + " hacked");
        }
        else {
            //System.out.println(cn.name + " nonhacked");
        }
        
        final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        cn.accept(cw);
        
        return cw.toByteArray();
        
    }
    

    public boolean containsMethod(final ClassNode cn, final String getterName,
            final String type, final boolean _static) {
        final Iterator<MethodNode> mt = cn.methods.iterator();
        while (mt.hasNext()) {
            final MethodNode next = mt.next();
            if (next.name.equals(getterName) && next.desc.equals(type)
                    && next.isMethodStatic() == _static)
                return true;
        }
        return false;
    }
}
